#include <linux/module.h>
#include <linux/slab.h>
#include <linux/rhashtable.h>
#include <linux/list.h>
#include "rwfile.h"
#include "utils.h"

typedef uint32_t bn_t;
#define FPSZ 128
#define BOFFSET_BITS 8
#define BINDEX(x) (x >> BOFFSET_BITS)
#define BOFFSET(x) (x & ((1 << BOFFSET_BITS) - 1))
#define GEN_ENTRYID(bindex, boffset) ((bindex << BOFFSET_BITS) | boffset)

struct metadata_entry {
    char fp[FPSZ];
    bn_t pbn;
};

#define ENTRY_PER_BLK_DEF (BLKSZ\
        -sizeof(struct list_head)-sizeof(bn_t)-sizeof(uint32_t))\
        / sizeof(struct metadata_entry)
const uint32_t ENTRY_PER_BLK = ENTRY_PER_BLK_DEF;

/**
 * 凑齐一个BLKSZ的entry块
 * TODO: 考虑写入磁盘时去掉list_head, 研究内存对齐与磁盘对齐的影响.
 *       目前一个可行的方案是添加一个由list_head和*metadata_blk组成的结构体
*/
struct metadata_blk {
    struct list_head list_head; // 把list放在里面是考虑到内存对齐性
    bn_t blockid;
    uint32_t valid_num;
    struct metadata_entry entries[ENTRY_PER_BLK_DEF];
}__attribute__((aligned(BLKSZ)));

struct refcount_blk_s { // 这里的_s表示small, 因为实际上不到一个块
    struct list_head list_head;
    bn_t block_id;
    char ref[ENTRY_PER_BLK_DEF];
};

// #define REF_PER_BLK_DEF (BLKSZ - sizeof(uint32_t))
//         / sizeof(struct refcount_blk_s)
// const uint32_t REF_PER_BLK = REF_PER_BLK_DEF;

// struct refcount_blk {
//     struct list_head list_head;
//     struct refcount_blk_s refcount_blk_s[REF_PER_BLK_DEF];
// }__attribute__((aligned(BLKSZ)));

struct LBN2PBN {
	struct rhash_head node;
	bn_t lbn; // key
	bn_t entryid; // value
};

const struct rhashtable_params l2p_params = {
    .key_len = sizeof(bn_t),
    .key_offset = offsetof(struct LBN2PBN, lbn),
    .head_offset = offsetof(struct LBN2PBN, node),
    .automatic_shrinking = true,
};

struct fp2PBN {
	struct rhash_head node;
	char fp[FPSZ]; // key
	bn_t entryid; // value
};

const struct rhashtable_params f2p_params = {
    .key_len = FPSZ * sizeof(char),
    .key_offset = offsetof(struct fp2PBN, fp),
    .head_offset = offsetof(struct fp2PBN, node),
    .automatic_shrinking = true,
};

/**
 * 系统中留存的matadata block的数量
 * 极端情况(所有块都不重复且填满): 32位地址空间 * 4KB / 每个metablk相当于多少datablk * 1%(经验比例) / 块大小
 * 计算结果大约为211,574 即约800M内存(相对于16TB存储空间)
 * 
 * 暂时设定一个简单的阈值, 且单独分配
 * TODO: 可以考虑在程序开始时集中分配, 但目前有技术难点: 1.与metadata_curr的逻辑矛盾 2.free困难, 不能单独free, 只能一起free
*/
// const uint64_t metablk_keep = (1L << (32 + 12)) / ENTRY_PER_BLK / 100 / BLKSZ;
const uint32_t metablk_keep = 10;
uint32_t metablk_inMem;

struct refcount_diff {
    bn_t entryid;
    char diff;
};

/**
 * 引用计数的差量
 * TODO: 如果禁止entryid为0, 即可直接去掉用于统计当前refcount_diff_blk中已有条目数的refcount_diff_num, 反正都要遍历一遍
 * TODO: refcount放到硬盘之后, 无法将其与正常的entry_blk进行区分
*/
const uint32_t REFCOUNT_DIFF_PER_BLK = BLKSZ / sizeof(struct refcount_diff);
uint32_t refcount_diff_num;
struct refcount_diff *refcount_diff_blk;

struct rhashtable l2p_rht, f2p_rht;
static LIST_HEAD(metahead);
static LIST_HEAD(refhead);
bn_t datacnt, metacnt;
struct metadata_blk *metadata_curr;
struct refcount_blk_s *ref_curr;

uint32_t rDedupFail = 0;
uint32_t rFail = 0;

static char *src = NULL; // 模块参数 把module_param放到.c后面是为了防止大范围报错(吐槽若至vscode)
